package com.wg.db.schema;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import com.wg.db.exception.DataException;
import com.wg.db.FormatTimeUnit;
import com.wg.db.annotation.EntityTable;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实体映射对象信息
 * 用于描述数据库表与Java实体类之间的映射关系
 *
 * @author 少爷123
 */
public class SchemaTable {
    /**
     * 实体类Class对象
     */
    private final Class<?> klass;
    /**
     * 表列信息集合
     */
    private final List<SchemaColumn> columns = new ArrayList<>();
    /**
     * 表名
     */
    private String tableName;
    /**
     * 主键字段
     */
    private SchemaColumn primaryField;
    /**
     * 分组字段
     */
    private SchemaColumn groupField;
    /**
     * 时间格式化单位
     */
    private FormatTimeUnit timeUnit;
    /**
     * 数据库配置
     */
    private Db db;
    /**
     * 线程安全的日期格式化
     */
    private final transient ThreadLocal<Map<String, SimpleDateFormat>> dateFormatCache = ThreadLocal
            .withInitial(ConcurrentHashMap::new);

    public SchemaTable(Class<?> klass) {
        this.klass = Objects.requireNonNull(klass, "klass must not be null");
        validateKlass();
    }

    private void validateKlass() {
        if (!klass.isAnnotationPresent(EntityTable.class)) {
            throw new DataException("Class [" + klass.getName() + "] is not an entity class");
        }
    }

    public Class<?> getKlass() {
        return klass;
    }

    public SchemaColumn getPrimaryField() {
        return primaryField;
    }

    public void setPrimaryField(SchemaColumn primaryField) {
        this.primaryField = primaryField;
    }

    public SchemaColumn getGroupField() {
        return groupField;
    }

    public void setGroupField(SchemaColumn groupField) {
        this.groupField = groupField;
    }

    public List<SchemaColumn> getColumns() {
        return Collections.unmodifiableList(columns);
    }

    public String getRealTableName() {
        if (timeUnit == FormatTimeUnit.NONE) {
            return tableName;
        } else {
            return getTableNameByFormat(0);
        }
    }

    public String getModelTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = StrUtil.isBlank(tableName) ? klass.getSimpleName() : tableName;
    }

    public void addSchemaColumn(SchemaColumn column) {
        Objects.requireNonNull(column, "column must not be null");
        columns.add(column);
    }

    public FormatTimeUnit getTimeUnit() {
        return timeUnit;
    }

    public void setTimeUnit(FormatTimeUnit timeUnit) {
        this.timeUnit = timeUnit;
    }

    public Db getDb() {
        return db;
    }

    public void setDb(Db db) {
        this.db = db;
    }

    /**
     * 获取实体ID（主键值）
     */
    @SuppressWarnings("unchecked")
    public <T extends Serializable> T getEntityId(Object obj) {
        if (primaryField == null) {
            throw new DataException("Entity [" + klass.getName() + "] has no primary key");
        }
        return (T) ReflectUtil.getFieldValue(obj, primaryField.getField());
    }

    /**
     * 获取分组字段值
     */
    @SuppressWarnings("unchecked")
    public <T extends Serializable> T getGroupId(Object obj) {
        if (groupField == null) {
            throw new DataException("Entity [" + klass.getName() + "] has no group field");
        }
        return (T) ReflectUtil.getFieldValue(obj, groupField.getField());
    }

    /**
     * 获取带时间分区的表名（线程安全）
     */
    public String getTableNameByFormat(int increase) {
        if (timeUnit == null || StrUtil.isBlank(tableName)) {
            return tableName;
        }

        Date baseDate = new Date();
        Date offsetDate = timeUnit.offset(baseDate, increase);
        String formatPattern = timeUnit.getFormatStr();

        Map<String, SimpleDateFormat> formatCache = dateFormatCache.get();
        SimpleDateFormat sdf = formatCache.computeIfAbsent(formatPattern, SimpleDateFormat::new);

        return tableName + "_" + sdf.format(offsetDate);
    }
}